#include "StdAfx.h"
#include "M2Manager.h"

ADTModel::ADTModel(std::string szFile) : ModelBase(1)
{
	m_fileName = szFile;
	m_pHeader = 0;
	m_file = new CMpqFile(szFile);
	if(!m_file || !m_file->IsOpened())
		return;
	m_buffer = new FileBuffer(*m_file);
}

ui32 ADTModel::LoadChunk(ui32& sig, ui32& len)
{
	if(!m_file)
		return ui32(-1);
	sig = 0;
	m_file->ReadInc(4, &sig);
	if(sig == 0)
		return 0;
	m_file->ReadInc(4, &len);
	if(len == 0)
		return 1;
	if(m_file->TellInc() + len > m_file->GetSize())
		return 0;
	return 1;
}

bool ADTModel::Init(bool initial)
{
	if(!m_file || !m_file->IsOpened())
		return false;
	m_file->SetInc(0x14);
	m_file->ReadInc(sizeof(MHDR), &m_header);
	m_file->SetInc(0x14 + 0x8 + m_header.ofsMCIN);
	for(ui32 i = 0; i < 256; ++i)
	{
		ADTMapChunk chnk;
		m_file->ReadInc(sizeof(ADTMapChunk), &chnk);
		m_chunkOfs.push_back(chnk);
	}

	m_file->SetInc(0x14 + 0x4 + m_header.ofsMTEX);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	char* tex = (char*)m_file->GetCurPointer();
	ui32 pos = 0;
	while(pos < size)
	{
		m_textures.push_back(tex);
		pos += strlen(tex) + 1;
		tex += strlen(tex) + 1;
	}

	mLoader.addTextures(m_textures);

	LoadDoodads();
	LoadDoodadRefs();
	LoadDoodadPlacement();

	if(m_header.ofsMH2O)
	{
		//Log::Print("Loading MH2O for ADT %s\n", this->m_fileName.c_str());
		//m_pHandler = new LiquidHandler(m_file, m_header.ofsMH2O, Vertex2F(m_indexX * TILESIZE, m_indexZ * TILESIZE));
	}
	else
		m_pHandler = 0;

	LoadWmos();
	LoadWmoRefs();

	m_chunksLoaded = 0;

	for(ui32 i = 0; i < 256; ++i)
	{
		ADTChunk* chnk = new ADTChunk(m_chunkOfs[i].offset, m_file, this, m_buffer, true);
		m_chunks.push_back(chnk);
		m_chunksLoaded++;
	}
	
	if(m_pHeader && m_pHeader->flags & 0x4)
	{
		for(ui32 i = 0; i < 256; ++i)
		{
			m_chunks[i]->NormalizeAlphas();
		}
	}

	//m_textureBlock.InitTextureBlock();

	//sD3D.AddMesh(this);
	mLoader.beginLoad();
	//LoadWmoPlacement();
	return true;
}

void ADTModel::Draw(Vertex3F pos, Vertex3F tar)
{
	sD3D->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
	//if(m_pHandler)
	//	m_pHandler->Render();
	for(ui32 i = 0; i < 256; ++i)
	{
		m_chunks[i]->Draw(pos, tar);
	}
}

void ADTModel::LoadDoodads()
{
	m_file->SetInc(m_header.ofsMMDX + 0x18);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	ui32 pos = 0;
	char* tex = (char*)m_file->GetCurPointer();
	while(pos < size)
	{
		m_doodads[pos] = tex;
		pos += strlen(tex) + 1;
		tex += strlen(tex) + 1;
	}
}

void ADTModel::LoadWmos()
{
	m_file->SetInc(m_header.ofsMWMO + 0x18);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	ui32 pos = 0;
	char* tex = (char*)m_file->GetCurPointer();
	while(pos < size)
	{
		m_wmos[pos] = tex;
		pos += strlen(tex) + 1;
		tex += strlen(tex) + 1;
	}
}

void ADTModel::LoadDoodadRefs()
{
	m_file->SetInc(0x14 + m_header.ofsMMID + 0x4);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	for(ui32 i = 0; i < (size / 4); ++i)
	{
		ui32 ref = 0;
		m_file->ReadInc(4, &ref);
		m_doodadrefs.push_back(ref);
	}
}

void ADTModel::LoadWmoRefs()
{
	m_file->SetInc(0x14 + m_header.ofsMWID + 0x4);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	for(ui32 i = 0; i < (size / 4); ++i)
	{
		ui32 ref = 0;
		m_file->ReadInc(4, &ref);
		m_wmorefs.push_back(ref);
	}
}

void ADTModel::changeDoodadPosition(ui32 id, const Vertex3F& newPos) {
	if(mDoodads.find(id) != mDoodads.end()) {
		sM2Mgr.changeInstancePosition(mDoodads[id].modelName, mDoodads[id].instanceEntry, newPos);
	}
}

void ADTModel::LoadDoodadPlacement()
{
	m_file->SetInc(0x14 + m_header.ofsMDDF + 0x4);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	for(ui32 i = 0; i < (size / sizeof(MDDF)); ++i)
	{
		MDDF mdf;
		m_file->ReadInc(sizeof(MDDF), &mdf);
		m_mddf[i] = mdf;
		
	}
}

void ADTModel::LoadWmoPlacement()
{
	m_file->SetInc(0x14 + m_header.ofsMODF + 0x4);
	ui32 size = 0;
	m_file->ReadInc(4, &size);
	for(ui32 i = 0; i < (size / sizeof(MODF)); ++i)
	{
		MODF mdf;
		m_file->ReadInc(sizeof(MODF), &mdf);
		m_modf.push_back(mdf);
		std::string doodad = m_wmos[m_wmorefs[mdf.nameOffset]];
		WMOModel* mod = new WMOModel(doodad, mdf);
		mod->SetTranslation(mdf.position[0], mdf.position[1], mdf.position[2]);
		//mod->SetRotation(mdf.rotation[2], -mdf.rotation[1], mdf.rotation[0]);
		mod->SetRotation(mdf.rotation[0], mdf.rotation[1] + D3DXToRadian(90), mdf.rotation[2]);
		mod->Init();
		m_wmoModels[mdf.uniqueId] = mod;
	}
}

void ADTModel::loadDoodad(ui32 refId) {
	if(mDoodads.find(refId) == mDoodads.end()) {
		auto itr = m_mddf.find(refId);
		if(itr == m_mddf.end()) 
			return;

		auto mdf = itr->second;
		std::string doodad = m_doodads[m_doodadrefs[mdf.nameOffset]];
		size_t pos = doodad.rfind('.');
		doodad = doodad.substr(0, pos);
		doodad += ".m2";
		std::transform(doodad.begin(), doodad.end(), doodad.begin(), tolower);
		ADTDoodadEntry de;
		de.modelName = doodad;
		de.doodadPlacement = mdf;
		de.instanceEntry = sM2Mgr.addInstance(doodad, mdf);
		mDoodads[mdf.uniqueId] = de;
	}
}

void ADTModel::Deinit()
{
	if(m_file)
		delete m_file;
	for(ui32 i = 0; i < 256; ++i)
	{
		m_chunks[i]->Deinit();
		delete m_chunks[i];
	}
	if(m_buffer)
		delete m_buffer;

	auto itr = mDoodads.begin();
	for( ; itr != mDoodads.end(); ++itr) {
		sM2Mgr.removeInstance(itr->second.modelName, itr->second.instanceEntry);
	}

	/*std::map<ui32, M2Model*>::iterator itr = m_doodModels.begin();
	std::map<ui32, M2Model*>::iterator end = m_doodModels.end();

	for( ; itr != end; ++itr)
	{
		if(itr->second)
		{
			itr->second->Deinit();
			sD3D.RemoveMesh(itr->second);
			//delete itr->second;
		}
	}*/

	m_doodModels.clear();
}

bool ADTModel::Intersect(D3DXVECTOR3& rayO, D3DXVECTOR3& rayDir, float* pOut)
{
	float curDist = 99999.0f;
	for(ui32 i = 0; i < 256; ++i)
	{
		if(m_chunks[i]->Intersect(rayO, rayDir, pOut))
		{
			if(*pOut < curDist)
				curDist = *pOut;
		}
	}
	
	if(curDist != 99999.0f)
		*pOut = curDist;
	return curDist != 99999.0f;
}

void SaveChunk(ADTChunk* chunk)
{
	chunk->Save();
}

void ADTModel::Save()
{
	std::string szFileName = m_fileName;
	std::string s2 = szFileName;
	std::transform(s2.begin(), s2.end(), s2.begin(), tolower);
	std::string::size_type t = szFileName.find("northrend");
	t = szFileName.rfind('\\');
	std::string::size_type t2 = szFileName.find('_', t);
	std::string sMap = szFileName.substr(t + 1, (t2 - t - 1));
	CreateDirectory("Save", 0);
	char curDirec[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, curDirec);
	SetCurrentDirectory(".\\Save\\");
	CreateDirectory("World", 0);
	SetCurrentDirectory("./World");
	CreateDirectory("Maps", 0);
	SetCurrentDirectory("./Maps");
	CreateDirectory(sMap.c_str(), 0);
	std::string nD = "./";
	nD += sMap;
	SetCurrentDirectory(nD.c_str());
	m_buffer->Write(0x14, sizeof(m_header), &m_header);
	std::string::size_type pos = m_fileName.rfind('\\');
	Log::Print("Saving ADT %s...", m_fileName.c_str());
	if(pos != std::string::npos)
	{
		std::string name = m_fileName.substr(pos + 1);
		for(ui32 i = 0; i < 256; ++i)
			SaveChunk(m_chunks[i]);

		if(m_mddf.size())
			m_buffer->Write(0x14 + m_header.ofsMDDF + 0x8, m_mddf.size() * sizeof(MDDF), &m_mddf[0]);
		if(m_modf.size())
			m_buffer->Write(0x14 + m_header.ofsMODF + 0x8, m_modf.size() * sizeof(MODF), &m_modf[0]);
		m_buffer->SaveToFile(name);
	}
	Log::Print("Finished!\n");
	SetCurrentDirectory(curDirec);
}

ui32 ADTModel::AddTexture(std::string szTexture)
{
	ui32 offset = 0;
	for(ui32 i = 0; i < m_textures.size(); ++i)
	{
		if(!stricmp(szTexture.c_str(), m_textures[i].c_str()))
			return i;
		offset += strlen(m_textures[i].c_str()) + 1;
	}

	m_textures.push_back(szTexture);
	szTexture.push_back(0);
	m_buffer->Append(0x14 + m_header.ofsMTEX + 0x08 + offset, strlen(szTexture.c_str()) + 1, (void*)szTexture.c_str());
	RecalcOffsets(0x14 + m_header.ofsMTEX + 0x08 + offset, strlen(szTexture.c_str()) + 1);
	offset += strlen(szTexture.c_str()) + 1;
	m_buffer->Write(0x14 + m_header.ofsMTEX + 0x04, 4, &offset);
	return m_textures.size() - 1;
}

void ADTModel::RecalcOffsets(ui32 pos, i32 change)
{
	if(m_header.ofsMCIN + 0x14 >= pos)
		m_header.ofsMCIN += change;
	if(m_header.ofsMDDF + 0x14 >= pos)
		m_header.ofsMDDF += change;
	if(m_header.ofsMFBO + 0x14 >= pos)
		m_header.ofsMFBO += change;
	if(m_header.ofsMH2O + 0x14 >= pos)
		m_header.ofsMH2O += change;
	if(m_header.ofsMMDX + 0x14 >= pos)
		m_header.ofsMMDX += change;
	if(m_header.ofsMMID + 0x14 >= pos)
		m_header.ofsMMID += change;
	if(m_header.ofsMODF + 0x14 >= pos)
		m_header.ofsMODF += change;
	if(m_header.ofsMTEX + 0x14 >= pos)
		m_header.ofsMTEX += change;
	if(m_header.ofsMTFX + 0x14 >= pos)
		m_header.ofsMTFX += change;
	if(m_header.ofsMWID + 0x14 >= pos)
		m_header.ofsMWID += change;
	if(m_header.ofsMWMO + 0x14 >= pos)
		m_header.ofsMWMO += change;

	for(ui32 i = 0; i < 256; ++i)
	{
		if(m_chunkOfs[i].offset >= pos)
		{
			m_chunkOfs[i].offset += change;
			if(i < m_chunksLoaded)
				m_chunks[i]->SetBaseOffset(m_chunkOfs[i].offset);
		}
	}

	m_buffer->Write(m_header.ofsMCIN + 0x14 + 0x8, 256 * sizeof(ADTMapChunk), &m_chunkOfs[0]);
}

void ADTModel::ChangeHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	for(ui32 i = 0; i < 256; ++i)
		m_chunks[i]->ChangeHeight(pos, radius, amount, type);

	if(!sSelection.IsAlignEnabled())
		return;

	/*std::map<ui32, MDDF>::iterator itr = m_mddf.begin();
	std::map<ui32, MDDF>::iterator end = m_mddf.end();

	for( ; itr != end; ++itr)
	{
		float dx = pos.x - itr->second.position[0];
		float dz = pos.z - itr->second.position[2];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			itr->second.position[1] = sD3D.GetLandHeight(itr->second.position[0], itr->second.position[2]);
			//if(m_doodModels[itr->second.uniqueId])
			//	m_doodModels[itr->second.uniqueId]->SetTranslation(itr->second.position[2], itr->second.position[1], itr->second.position[0]);
		}
	}*/

	auto itr = mDoodads.begin();
	for( ; itr != mDoodads.end(); ++itr) {
		float dx = pos.x - itr->second.doodadPlacement.position[0];
		float dz = pos.z - itr->second.doodadPlacement.position[2];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius) {
			sD3D.getLandHeightFast(itr->second.doodadPlacement.position[0], itr->second.doodadPlacement.position[2], itr->second.doodadPlacement.position[1]);
			changeDoodadPosition(itr->first, itr->second.doodadPlacement.position);
		}
	}

	std::vector<MODF>::iterator itr2 = m_modf.begin();
	std::vector<MODF>::iterator end2 = m_modf.end();

	for( ; itr2 != end2; ++itr2)
	{
		float dx = pos.x - (*itr2).position[2];
		float dz = pos.z - (*itr2).position[0];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			(*itr2).position[1] = sD3D.GetLandHeight((*itr2).position[0], (*itr2).position[2]);
			if(m_wmoModels[(*itr2).uniqueId])
				m_wmoModels[(*itr2).uniqueId]->SetTranslation((*itr2).position[2], (*itr2).position[1], (*itr2).position[0]);
		}
	}
}

void ADTModel::FlattenHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	for(ui32 i = 0; i < 256; ++i)
		m_chunks[i]->FlattenHeight(pos, radius, amount, type);

	if(!sSelection.IsAlignEnabled())
		return;

	std::map<ui32, MDDF>::iterator itr = m_mddf.begin();
	std::map<ui32, MDDF>::iterator end = m_mddf.end();

	for( ; itr != end; ++itr)
	{
		float dx = pos.x - itr->second.position[2];
		float dz = pos.z - itr->second.position[0];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			itr->second.position[1] = sD3D.GetLandHeight(itr->second.position[0], itr->second.position[2]);
			//if(m_doodModels[itr->second.uniqueId])
			//	m_doodModels[itr->second.uniqueId]->SetTranslation(itr->second.position[2], itr->second.position[1], itr->second.position[0]);
		}
	}

	std::vector<MODF>::iterator itr2 = m_modf.begin();
	std::vector<MODF>::iterator end2 = m_modf.end();

	for( ; itr2 != end2; ++itr2)
	{
		float dx = pos.x - (*itr2).position[2];
		float dz = pos.z - (*itr2).position[0];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			(*itr2).position[1] = sD3D.GetLandHeight((*itr2).position[0], (*itr2).position[2]);
			if(m_wmoModels[(*itr2).uniqueId])
				m_wmoModels[(*itr2).uniqueId]->SetTranslation((*itr2).position[2], (*itr2).position[1], (*itr2).position[0]);
		}
	}
}

void ADTModel::BlurHeight(Vertex3F pos, float radius, float amount, ui32 type)
{
	for(ui32 i = 0; i < 256; ++i)
		m_chunks[i]->BlurHeight(pos, radius, amount, type);

	if(!sSelection.IsAlignEnabled())
		return;

	std::map<ui32, MDDF>::iterator itr = m_mddf.begin();
	std::map<ui32, MDDF>::iterator end = m_mddf.end();

	for( ; itr != end; ++itr)
	{
		float dx = pos.x - itr->second.position[2];
		float dz = pos.z - itr->second.position[0];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			itr->second.position[1] = sD3D.GetLandHeight(itr->second.position[0], itr->second.position[2]);
			//if(m_doodModels[itr->second.uniqueId])
			//	m_doodModels[itr->second.uniqueId]->SetTranslation(itr->second.position[2], itr->second.position[1], itr->second.position[0]);
		}
	}

	std::vector<MODF>::iterator itr2 = m_modf.begin();
	std::vector<MODF>::iterator end2 = m_modf.end();

	for( ; itr2 != end2; ++itr2)
	{
		float dx = pos.x - (*itr2).position[2];
		float dz = pos.z - (*itr2).position[0];
		float dist = sqrtf(dx * dx + dz * dz);
		if(dist < radius)
		{
			(*itr2).position[1] = sD3D.GetLandHeight((*itr2).position[0], (*itr2).position[2]);
			if(m_wmoModels[(*itr2).uniqueId])
				m_wmoModels[(*itr2).uniqueId]->SetTranslation((*itr2).position[2], (*itr2).position[1], (*itr2).position[0]);
		}
	}
}

void ADTModel::AddMTFX()
{
	ui8* pData = new ui8[m_textures.size() * 4 + 8];
	*(ui32*)pData = 'MTFX';
	*(ui32*)(pData + 4) = m_textures.size() * 4;
	for(ui32 i = 0; i < m_textures.size(); ++i)
	{
		if(i + 1 == m_textures.size())
			*(ui32*)(pData + i * 4 + 8) = 1;
		else
			*(ui32*)(pData + i * 4 + 8) = 0;
	}
	m_header.ofsMTFX = m_buffer->GetSize() - 0x14;
	m_buffer->Append(m_buffer->GetSize(), m_textures.size() * 4 + 8, pData);
}

ui32 ADTModel::AddWMOModel(std::string szName)
{
	ui32 size;
	m_buffer->Read(0x14 + m_header.ofsMWMO + 0x04, 4, &size);
	szName.push_back(0);
	m_buffer->Append(0x14 + m_header.ofsMWMO + 0x08 + size, strlen(szName.c_str()) + 1, (void*)szName.c_str());
	RecalcOffsets(0x14 + m_header.ofsMWMO + 0x08 + size, strlen(szName.c_str()) + 1);
	size += strlen(szName.c_str()) + 1;
	m_buffer->Write(0x14 + m_header.ofsMWMO + 0x04, 4, &size);
	m_wmos[m_wmos.size()] = szName;
	return m_wmos.size();
}

ui32 ADTModel::CreateWmoId(ui32 wmoNum)
{
	ui32 size;
	m_buffer->Read(0x14 + m_header.ofsMWID + 0x04, 4, &size);
	m_buffer->Append(0x14 + m_header.ofsMWID + 0x08 + size, 4, &wmoNum);
	RecalcOffsets(0x14 + m_header.ofsMWID + 0x08 + size, 4);
	size += 4;
	m_buffer->Write(0x14 + m_header.ofsMWID + 0x04, 4, &size);
	m_wmorefs.push_back(wmoNum);
	return m_wmorefs.size();
}

ui32 ADTModel::CreateMODF(ui32 wmoId, Vertex3F position)
{
	MODF m;
	m.doodadSet = 0;
	m.flags = 0;
	m.lowerExt[0] = position.x - 1;
	m.lowerExt[1] = position.y - 1;
	m.lowerExt[2] = position.z - 1;


	return 0;
}

void ADTModel::Update(ui32 diff) {
	for(ui32 i = 0; i < 256; ++i) {
		m_chunks[i]->Update(diff);
	}
}